
import java.util.*;

/**
 * Clase para manejar las instrucciones dadas.
 *
 * @author Villalva Bueno Arely Eunice
 * @author Romero Rodriguez Gabriel Eduardo
 */
public class Instruction {

    public int opcode;
    public int rs;
    public int rt;
    public int rd;
    public int immediate;
    public int rdValue;
    public int rsValue;
    public int rtValue;
    public String instructionString;
    private boolean ok;
    public boolean flush;
    public boolean branchTaken;
    public boolean wbFlush;
    public boolean forwardRsFlag;
    public boolean forwardRtFlag;
    private boolean isImmediateInstruction;
    private String error = "La instrucción introducida es inválida.";

    /**
     * Crea una instrucción a partir de el contenido de la cadena.
     */
    Instruction(String temp) {
        StringTokenizer tokens = new StringTokenizer(temp, " ");
        String op = "", t1 = "", t2 = "", t3 = "";
        int switcher;

        rdValue = 0;              // valores iniciales
        rsValue = 0;
        rtValue = 0;
        isImmediateInstruction = false;  // asume que no es una instrucción inmediata.
        ok = true;
        instructionString = temp; // la instruccion completa como cadena.
        flush = false;            // instrucción sera ejecutada.
        branchTaken = true;
        forwardRsFlag = false;    // assume que no será usado el fowarding
        forwardRtFlag = false;

        if (temp.equals("NOP")) {
            instructionString = "NOP";
            rd = 0;
            rt = 0;
            rs = 0;
            immediate = 0;
            opcode = 0;
            return;
        }

        trying:
        try {
            op = tokens.nextToken();
            t1 = tokens.nextToken();
            t2 = tokens.nextToken();

            if (t1.length() < 2 || t2.length() < 2) {
                ok = false;
                break trying;
            }

            if (!op.equals("LW") && !op.equals("SW")) {
                t3 = tokens.nextToken();
                if (t3.length() == 0) {
                    ok = false;
                    break trying;
                }
            }

            if (op.equals("ADD") || op.equals("SUB") || op.equals("MULT") ||
                    op.equals("DIV") || op.equals("AND") || op.equals("OR") ||
                    op.equals("XOR") || op.equals("SLL") || op.equals("SRL") ||
                    op.equals("SLT") || op.equals("SLE") || op.equals("SGT") ||
                    op.equals("SGE") || op.equals("SEQ")) {
                switcher = 0;  // R-type instrucciones
            } else if (op.equals("ADDI") || op.equals("ANDI") || op.equals("ORI") ||
                    op.equals("XORI") || op.equals("SUBI") || op.equals("SLLI") ||
                    op.equals("SRLI")) {
                switcher = 1;  // I-type instrucciones
            } else if (op.equals("LW") || op.equals("SW")) {
                switcher = 2;  // load/store word instrucciones
            } else if (op.equals("BEQ") || op.equals("BNE")) {
                switcher = 3;  // branch instrucciones
            } else {
                ok = false;
                break trying;
            }

            switch (switcher) {
                case 0:
                    if (!t1.startsWith("$") || !t2.startsWith("$") ||
                            !t1.startsWith("$")) {
                        ok = false;
                        break trying;
                    } else {
                        rd = Integer.parseInt(t1.substring(1));
                        rs = Integer.parseInt(t2.substring(1));
                        rt = Integer.parseInt(t3.substring(1));
                        if (rt < 0 || rt > 31 || rs < 0 || rs > 31 || rd < 1 || rd > 31) {
                            error = "Instrucción inválida: registro fuera de rango";
                            ok = false;
                            break trying;
                        }
                        if (op.equals("ADD")) {
                            opcode = 32;
                        } else if (op.equals("SUB")) {
                            opcode = 34;
                        } else if (op.equals("MULT")) {
                            opcode = 24;
                        } else if (op.equals("DIV")) {
                            opcode = 26;
                        } else if (op.equals("AND")) {
                            opcode = 36;
                        } else if (op.equals("XOR")) {
                            opcode = 38;
                        } else if (op.equals("SLL")) {
                            opcode = 50;
                        } else if (op.equals("SRL")) {
                            opcode = 51;
                        } else if (op.equals("SLT")) {
                            opcode = 60;
                        } else if (op.equals("SLE")) {
                            opcode = 61;
                        } else if (op.equals("SEQ")) {
                            opcode = 62;
                        } else if (op.equals("SGT")) {
                            opcode = 63;
                        } else if (op.equals("SGE")) {
                            opcode = 64;
                        } else {
                            opcode = 37;
                        }
                    }
                    break;
                case 1:
                    if (!t1.startsWith("$") || !t2.startsWith("$")) {
                        ok = false;
                        break trying;
                    } else {
                        rd = Integer.parseInt(t1.substring(1));
                        rs = Integer.parseInt(t2.substring(1));
                        if (rd < 1 || rd > 31 || rs < 0 || rs > 31) {
                            error = "";
                            ok = false;
                            break trying;
                        }
                        isImmediateInstruction = true;
                        immediate = Integer.parseInt(t3);
                        if (op.equals("ADDI")) {
                            opcode = 32;
                        } else if (op.equals("ANDI")) {
                            opcode = 36;
                        } else if (op.equals("SUBI")) {
                            opcode = 34;
                        } else if (op.equals("XORI")) {
                            opcode = 38;
                        } else if (op.equals("SLLI")) {
                            opcode = 50;
                        } else if (op.equals("SRLI")) {
                            opcode = 51;
                        } else {
                            opcode = 37; 
                        }
                    }
                    break;
                case 2:
                    if (!t1.startsWith("$")) {
                        ok = false;
                        break trying;
                    } else {
                        rt = Integer.parseInt(t1.substring(1));
                        rs = Integer.parseInt(t2.substring(t2.indexOf("$") + 1, t2.indexOf(")")));
                        if (rt < 1 || rt > 31 || rs < 0 || rs > 31) {
                            error = "Instrucción inválida: registro fuera de rango";
                            ok = false;
                            break trying;
                        }
                        immediate = Integer.parseInt(t2.substring(0, t2.indexOf("(")));
                        if (op.equals("LW")) {
                            opcode = 35;
                            rd = rt;
                        } else {
                            opcode = 43;
                        }
                    }
                    break;
                case 3:
                    if (!t1.startsWith("$") || !t2.startsWith("$")) {
                        ok = false;
                        break trying;
                    } else {
                        rs = Integer.parseInt(t1.substring(1));
                        rt = Integer.parseInt(t2.substring(1));
                        if (rs < 1 || rs > 31 || rt < 0 || rt > 31) {
                            error = "Instrucción inválida: registro fuera de rango";
                            ok = false;
                            break trying;
                        }
                        isImmediateInstruction = false;
                        immediate = Integer.parseInt(t3);
                        if (op.equals("BEQ")) {
                            opcode = 70;
                        } else {
                            opcode = 71;
                        }
                    }
                    break;
                default:
                    ok = false;
                    break trying;
            }
        } catch (NumberFormatException e) {
            ok = false;
        } catch (NoSuchElementException e2) {
            ok = false;
        } catch (StringIndexOutOfBoundsException e3) {
            ok = false;
        }
    }

    /**
     * Indica si la instrucción es válida
     */
    public boolean valid() {
        return ok;
    }

    /**
     * Regresa una cadena con el error generado por la instrucción.
     */
    public String elError() {
        return error;
    }

    /**
     * Regresa un boolenao indicando si la instrucción es inmediata.
     */
    public boolean isImmediate() {
        return isImmediateInstruction;
    }

    /**
     * Regresa un cadena representando a la instrucción.
     */
    public String toString() {
        return (instructionString + "\n");
    }
}
